﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kaspersky.Test.RecursiveLinkLogger
{
    /// <summary>
    /// A deterministic finite state machine.
    /// Extracts HTML links (<a href="http//www.kaspersky.ru"></a>) from input stream of chars.
    /// Please, see LinkParsingStateMachine.activitydiagram in Kaspersky.Test.ModelingProject for 
    /// the state machine diagram.
    /// </summary>
	public class LinkParsingStateMachine
	{
        /// <summary>
        /// Represents abstract state
        /// </summary>
		protected abstract class State
		{
            // char arrays are a bit faster, than strings
            protected static readonly char[] Spaces = {' ', '\n', '\r', '\t'};
            protected const char OpeningBrace = '<';
            protected const char ClosingBrace = '>';
            
            protected LinkParsingStateMachine Machine;

			protected State(LinkParsingStateMachine machine)
			{
				this.Machine = machine;
			}

            /// <summary>
            /// Processes next symbol
            /// </summary>
            /// <param name="signal"></param>
			public abstract void Process(char signal);
		}

		protected class WaitingForOpeningBraceState : State
		{
            private static readonly char[] A = { 'a', 'A' };

			public WaitingForOpeningBraceState(LinkParsingStateMachine machine) : base(machine)
			{
			}

			public override void Process(char signal)
			{
			    if (signal == OpeningBrace)
			    {
			        this.Machine.CurrentState = new WaitingForCharState
			        (
			            this.Machine,
			            A,
			            new WaitingForSpaceState(this.Machine),
			            new WaitingForOpeningBraceState(this.Machine)
			        );
			    }
			}
		}

        protected class WaitingForSpaceState : State
        {
            public WaitingForSpaceState(LinkParsingStateMachine machine) : base(machine)
            {
            }

            public override void Process(char signal)
            {
                if (Spaces.Contains(signal))
                {
                    this.Machine.CurrentState = new WaitingForHState(this.Machine);
                }
                else
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
            }
        }

        protected class WaitingForSpaceOrClosingBraceState : State
        {
            public WaitingForSpaceOrClosingBraceState(LinkParsingStateMachine machine) : base(machine)
            {
            }

            public override void Process(char signal)
            {
                if (signal == ClosingBrace)
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
                else if (Spaces.Contains(signal))
                {
                    this.Machine.CurrentState = new WaitingForHState(this.Machine);
                }
            }
        }

		protected class WaitingForHState : State
		{
            private static readonly char[] H = { 'h', 'H' };
            private static readonly char[] R = { 'r', 'R' };
            private static readonly char[] E = { 'e', 'E' };
            private static readonly char[] F = { 'f', 'F' };

			public WaitingForHState(LinkParsingStateMachine machine) : base(machine)
			{
			}

			public override void Process(char signal)
			{
                if(Spaces.Contains(signal))
                {
                }
                else if (signal == ClosingBrace)
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
                else if (H.Contains(signal))
                {
                    this.Machine.CurrentState = new WaitingForCharOrClosingBraceState // waiting for "href" keyword
                    (
                        this.Machine,
                        R,
                        new WaitingForCharOrClosingBraceState
                        (
                            this.Machine,
                            E,
                            new WaitingForCharOrClosingBraceState
                            (
                                this.Machine,
                                F,
                                new WaitingForEqState(this.Machine)
                            )
                        )
                    );
                }
                else
                {
                    this.Machine.CurrentState = new WaitingForSpaceOrClosingBraceState(this.Machine);
                }
			}
		}

		protected class WaitingForCharState : State
		{
			private readonly char[] _chars;
            private readonly State _nextState;
            private readonly State _errorState;

			public WaitingForCharState(LinkParsingStateMachine machine, char[] chars, State nextState, State errorState) : base(machine)
			{
				this._chars = chars;
				this._nextState = nextState;
			    this._errorState = errorState;
			}

			public override void Process(char signal)
			{
			    if (this._chars.Contains(signal))
			    {
			        this.Machine.CurrentState = this._nextState;
			    }
			    else
			    {
			        this.Machine.CurrentState = this._errorState;
			    }
			}
		}

        protected class WaitingForCharOrClosingBraceState : State
        {
            private readonly char[] _chars;
            private readonly State _nextState;

            public WaitingForCharOrClosingBraceState(LinkParsingStateMachine machine, char[] chars, State nextState)
                : base(machine)
            {
                this._chars = chars;
                this._nextState = nextState;
            }

            public override void Process(char signal)
            {
                if (this._chars.Contains(signal))
                {
                    this.Machine.CurrentState = this._nextState;
                }
                else if (signal == ClosingBrace)
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
                else
                {
                    this.Machine.CurrentState = new WaitingForSpaceOrClosingBraceState(this.Machine);
                }
            }
        }

		protected class WaitingForEqState : State
		{
			private const char EqChar = '=';

			public WaitingForEqState(LinkParsingStateMachine machine) : base(machine)
			{
			}

			public override void Process(char signal)
			{
			    if (Spaces.Contains(signal))
			    {
			    }
			    else if (signal == EqChar)
			    {
			        this.Machine.CurrentState = new WaitingForLinkStartState(this.Machine);
			    }
                else if (signal == ClosingBrace)
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
                else
                {
                    this.Machine.CurrentState = new WaitingForSpaceOrClosingBraceState(this.Machine);
                }
			}
		}

		protected class WaitingForLinkStartState : State
		{
			private const char SingleQuote = '\'';
			private const char DoubleQuote = '\"';

			public WaitingForLinkStartState(LinkParsingStateMachine machine) : base(machine)
			{
			}

			public override void Process(char signal)
			{
			    if (signal == SingleQuote)
			    {
			        this.Machine.CurrentState = new ReadingLinkWithQuotesState(this.Machine, SingleQuote);
			    }
                else if (signal == DoubleQuote)
                {
                    this.Machine.CurrentState = new ReadingLinkWithQuotesState(this.Machine, DoubleQuote);
                }
                else if (!Spaces.Contains(signal))
                {
                    this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
                }
			}
		}

		protected class ReadingLinkWithQuotesState : State
		{
            private readonly char _quote;

			public ReadingLinkWithQuotesState(LinkParsingStateMachine machine, char quote) : base(machine)
			{
				this._quote = quote;
			}

			public override void Process(char signal)
			{
			    if (signal == this._quote)
			    {
                    // Wow, we've got the link! Raising an event to tell everybody about it.
			        this.Machine.RaiseLinkFound();

			        this.Machine.CurrentState = new WaitingForClosingBraceState(this.Machine);
			    }
			    else
			    {
			        this.Machine.AddToLink(signal);
			    }
			}
		}

		protected class WaitingForClosingBraceState : State
		{
			public WaitingForClosingBraceState(LinkParsingStateMachine machine) : base(machine)
			{
			}

			public override void Process(char signal)
			{
			    if (signal == ClosingBrace)
			    {
			        this.Machine.CurrentState = new WaitingForOpeningBraceState(this.Machine);
			    }
			}
		}

        /// <summary>
        /// Contains the machine's current state
        /// </summary>
	    protected State CurrentState;

        /// <summary>
        /// Here the link is accumulated
        /// </summary>
	    protected StringBuilder Link = new StringBuilder();

        /// <summary>
        /// Raised, when yet another link is found
        /// </summary>
        public event Action<string> LinkFoundEvent;

        /// <summary>
        /// Searches the input stream for HTML links
        /// </summary>
        /// <param name="input"></param>
        public void Parse(IEnumerable<char> input)
        {
            this.CurrentState = new WaitingForOpeningBraceState(this);

            foreach (var signal in input)
            {
                this.CurrentState.Process(signal);
            }

            if (!(this.CurrentState is WaitingForOpeningBraceState))
            {
                throw new ArgumentException("A syntax error in input string!");
            }
        }

        protected void AddToLink(char symbol)
        {
            this.Link.Append(symbol);
        }

        protected void RaiseLinkFound()
        {
            var link = this.Link.ToString();

            var handler = this.LinkFoundEvent;
            if ((handler != null) && (link.Length > 1))
            {
                handler(link);
            }

            this.Link.Clear();
        }
	}
}
